﻿using SharpDepend_UWP_DirectXInterop;
using SharpDepend.Datatypes;
using SharpDepend.WinRT.Classes;
using System;
using System.Collections.Generic;
using Windows.Graphics.Display;
using Windows.UI.Xaml.Controls;

namespace SharpDepend.WinRT.Renderer
{
    class DirectX2DRenderer : DirectXRenderer
    {
        public DirectX2DRenderer()
            : base(DirectXType.D2D)
        {
        }
    }

    class DirectX3DRenderer : DirectXRenderer
    {
        public DirectX3DRenderer()
            : base(DirectXType.D3D)
        {
        }
    }

    abstract class DirectXRenderer : Grid, IGraphicsDevice
    {
        public enum DirectXType
        {
            D2D,
            D3D
        }

        private SharpDepend_UWP_DirectXInterop.DirectXPanelBase mPanel;
        private List<Datatypes.Texture> mTextures;
        private System.Collections.ObjectModel.ReadOnlyCollection<Datatypes.Texture> mTexturesReadonly;
        private bool mEnable;
        private bool mRenderTextures;
        private double mDpiScale;

        public DirectXRenderer(DirectXType type)
        {
            RenderTextures = true;

            mTextures = new List<Datatypes.Texture>();
            mTexturesReadonly = new System.Collections.ObjectModel.ReadOnlyCollection<Datatypes.Texture>(mTextures);

            switch (type)
            {
                case DirectXType.D2D:
                    mPanel = new D2DPanel();
                    break;
                case DirectXType.D3D:
                    mPanel = new D3DPanel();
                    break;
                default:
                    throw new NotSupportedException(type.ToString());
            }
            Children.Add(mPanel);

            Loaded += DirectXRenderer_Loaded;
            SizeChanged += DirectXRenderer_SizeChanged;
        }

        bool mFirstInit = true;
        
        private void DirectXRenderer_SizeChanged(object sender, Windows.UI.Xaml.SizeChangedEventArgs e)
        {
            RenderWidth = (int)(mPanel.ActualWidth * mDpiScale);
            RenderHeight = (int)(mPanel.ActualHeight * mDpiScale);

            //if (mFirstInit)
            //{
            //    mFirstInit = false;

            //    Ready = true;

            //    foreach (var texture in mReadyTextures)
            //    {
            //        InternalRegisterTexture(texture);
            //    }
            //    mReadyTextures.Clear();
            //}

            Ready = true;
        }

        void DirectXRenderer_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            var currentView = DisplayInformation.GetForCurrentView();
            mDpiScale = Helper.GetPixelScale();

            RenderWidth = (int)(mPanel.ActualWidth * mDpiScale);
            RenderHeight = (int)(mPanel.ActualHeight * mDpiScale);
        }

        public void BeginRender()
        {
            if (mEnable)
            {
                if (mFirstInit)
                {
                    mFirstInit = false;
                    
                    foreach (var texture in mReadyTextures)
                    {
                        InternalRegisterTexture(texture);
                    }
                    mReadyTextures.Clear();
                }

                mPanel.BeginDraw();
            }
        }

        public void Draw(ref SharpDepend.Datatypes.Vector4 rectangle, SharpDepend.Datatypes.Color color, SharpDepend.Datatypes.Texture texture, ref SharpDepend.Datatypes.Vector4 destinationTextureUV)
        {
            if (mEnable)
            {
                if (rectangle.Y2 < 0 || rectangle.Y1 > RenderHeight ||
                    rectangle.X2 < 0 || rectangle.X1 > RenderWidth)
                {
                    return;
                }

                if (texture != null && !Textures.Contains(texture))
                {
                    //throw new Exception("DirectX is trying to render a texture that is not registrated.");
                    texture = null;
                    color = Datatypes.Color.Pink;
                }

                int tW = 0;
                int tH = 0;

                if (texture != null)
                {
                    tW = texture.Width;
                    tH = texture.Height;

                    //destinationTextureUV = new Datatypes.Vector4((float)(1.0 / texture.Width * destinationTextureUV.X), (float)(1.0 / texture.Height * destinationTextureUV.Y),
                    //    (float)(1.0 / texture.Width * destinationTextureUV.X2), (float)(1.0 / texture.Height * destinationTextureUV.Y2));
                }


                mPanel.DrawRectangle(
                    (float)rectangle.X1, (float)rectangle.Y1, (float)rectangle.X2, (float)rectangle.Y2,
                    (float)color.R, (float)color.G, (float)color.B, (float)color.A,
                    texture == null ? -1 : texture.ID,
                    (float)destinationTextureUV.X1, (float)destinationTextureUV.Y1, (float)destinationTextureUV.X2, (float)destinationTextureUV.Y2,
                    tW, tH);
            }
        }

        public void Draw(ref SharpDepend.Datatypes.Vector4 rectangle, SharpDepend.Datatypes.Color color)
        {
            if (mEnable)
            {
                Datatypes.Vector4 dummy = Datatypes.Vector4.ZeroOne;
                Draw(ref rectangle, color, null, ref dummy);
            }
        }

        public void DrawCircle(SharpDepend.Datatypes.Vector2 position, double radius, SharpDepend.Datatypes.Color color)
        {
            if (mEnable)
            {
                mPanel.DrawCircle(
                (float)position.X, (float)position.Y, (float)radius,
                (float)color.R, (float)color.G, (float)color.B, (float)color.A
                );
            }
        }

        public void DrawLine(SharpDepend.Datatypes.Vector2 start, SharpDepend.Datatypes.Vector2 end, SharpDepend.Datatypes.Color color)
        {
            if (mEnable)
            {
                mPanel.DrawLine(
                (float)start.X, (float)start.Y, (float)end.X, (float)end.Y,
                (float)color.R, (float)color.G, (float)color.B, (float)color.A
                );
            }
        }

        public void DrawTriangles(FastStructList<Datatypes.Vector2> vertices, FastStructList<ushort> indices, FastStructList<Datatypes.Color> colors)
        {
            if (mEnable)
            {
                if (indices.Count > 0)
                {
                    IntPtr verticesPtr = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(vertices.ToArray(), 0);
                    IntPtr colorsPtr = System.Runtime.InteropServices.Marshal.UnsafeAddrOfPinnedArrayElement(colors.ToArray(), 0);

                    long verticesPtr2 = verticesPtr.ToInt64();
                    long colorsPtr2 = colorsPtr.ToInt64();

                    mPanel.DrawTriangles(verticesPtr2, vertices.Count, out indices.ToArray()[0], indices.Count, colorsPtr2, colors.Count);
                }
            }
        }

        public void EndRender()
        {
            if (mEnable)
            {
                mPanel.EndDraw();
            }
        }

        public bool Enable
        {
            get
            {
                return mEnable;
            }
            set
            {
                mEnable = value;
            }
        }


        public bool RenderTextures
        {
            get
            {
                return mRenderTextures;
            }
            set
            {
                mRenderTextures = value;
            }
        }

        public object Locker
        {
            get { return this; }
        }
        
        public int RenderHeight { get; private set; }

        public int RenderWidth { get; private set; }

        public void ResetClip()
        {

        }

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        public void SetClip(SharpDepend.Datatypes.Vector4 plane)
        {

        }

        public void SetViewportSize(double width, double height)
        {

        }

        private List<Datatypes.Texture> mReadyTextures = new List<Datatypes.Texture>();

        public void RegisterTexture(Datatypes.Texture texture)
        {
            if (mTextures.Contains(texture))
            {
                throw new Exception("Texture already added.");
            }
            mTextures.Add(texture);

            if (!Ready)
            {
                mReadyTextures.Add(texture);
            }
            else
            {
                InternalRegisterTexture(texture);
            }
        }

        private void InternalRegisterTexture(Datatypes.Texture texture)
        {
            texture.OnEndUpdate += Texture_OnUpdated;

            lock (mPanel)
            {
                // Reference: http://stackoverflow.com/questions/9196520/passing-a-byte-array-from-c-sharp-into-c-com-object-with-c-filling-array
                // TODO: Verkar krascha när texture registreras innan directX har initisierats.
                // Fixa så att Read blir true när allt har initisierats :)

                // TODO: Verkar som hela DirectX har pajjat totalt -.-
                mPanel.RegisterTexture(texture.ID, out texture.Data[0], texture.Data.Length, texture.Width, texture.Height);
            }
        }

        private void Texture_OnUpdated(Datatypes.Texture texture)
        {
            lock (mPanel)
            {
                //mPanel.UnregisterTexture(texture.ID);
                //mPanel.RegisterTexture(texture.ID, out texture.Data[0], texture.Data.Length, texture.Width, texture.Height);
                mPanel.UpdateTexture(texture.ID, out texture.Data[0], texture.Width, texture.Height);
            }
        }

        public void UnregisterTexture(Datatypes.Texture texture)
        {
            if (!mTextures.Contains(texture))
            {
                throw new Exception("Texture can not be removed. It do not exist.");
            }
            mTextures.Remove(texture);

            if (!Ready)
            {
                mReadyTextures.Remove(texture);
            }
            else
            {
                texture.OnEndUpdate -= Texture_OnUpdated;

                lock (mPanel)
                {
                    mPanel.UnregisterTexture(texture.ID);
                }
            }
        }

        public void UpdateTexture(SharpDepend.Datatypes.Texture texture)
        {

        }

        public void DisposeDevice()
        {

        }

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        public System.Collections.ObjectModel.ReadOnlyCollection<SharpDepend.Datatypes.Texture> Textures
        {
            get { return mTexturesReadonly; }
        }

        public bool Ready { get; private set; }

        public bool ManualRendering
        {
            get
            {
                return false;
            }
        }
    }
}
